#!/usr/bin/env python
# encoding: utf-8

import logging
import time
import os
import platform
import re
import requests
import json
import configparser
from threading import Thread
"""日志文件，调试用mqtt.log"""
# if platform.system() == 'Linux':
#     logging.basicConfig(filename=r'/var/log/mqtt.log',
#                         format='%(asctime)s - %(levelname)s - %(message)s',
#                         level=logging.DEBUG)
# else:
logging.basicConfig(filename=os.getcwd() + '/log/mqtt.log',
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    level=logging.DEBUG)
import serial
import serial.tools.list_ports
import binascii
import json
from dbsession import Sql
sql = Sql()

localhost_cid = '53010000'

class Cmd:
    def __init__(self):
        self.header = 0xAA
        self.length = 0xFF
        # 数据包信息
        self.data_addr_taget = [0xFF,0xFF,0xFF]
        self.data_addr_local = [0xFF,0xFF,0xFF]
        self.data_type = 0xFF
        self.data_cmd = 0xFF
        self.data_content = []
        self._sum = 0xFF

    def get_cmd(self, target=[], local=[], _type =0xff, cmd=0xff, content=[] ):
        self.data_addr_taget = target
        self.data_addr_local = local
        self.data_type = _type
        self.data_cmd = cmd
        self.data_content = content
        # 计算数据长度
        self.length = len(self.data_addr_taget)+len(self.data_addr_local)+1+1+len(self.data_content)
        cmd = []
        cmd.append(self.header)
        cmd.append(self.length)
        for addr in self.data_addr_taget:
            cmd.append(addr)
        for addr in self.data_addr_local:
            cmd.append(addr)
        cmd.append(self.data_type)
        cmd.append(self.data_cmd)
        for addr in self.data_content:
            cmd.append(addr)
        # 计算校验和
        s = sum(self.data_addr_taget) + sum(self.data_addr_local) + self.data_type + self.data_cmd + sum(self.data_content)
        self._sum = s % 256
        cmd.append(self._sum)
        return cmd

    def get_cmd(self, target='',loacl='',_type='',cmd='',content=''):
        header = 'AA'
        data = target+local+_type+cmd+content
        length = hex(len(data)/2).replace('0x','').zfill(2)[-2:]
        data_list = [int(data[i*2:i*2+1], 16) for i in range(0,len(data)) if i%2==0]
        _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
        return header+length+data+_sum


    def recv_deal(self, recv):
        '''串口处理命令，监测校验和是否正确'''
        if recv[0] == 0xAA:
            data = recv[2:-1]
            _sum = recv[-1]
            if len(data) == recv[1]:
                s = sum(data) % 256
                if s == _sum:
                    r = {
                        'target_addr': recv[2:6],
                        'local_addr': recv[6:10],
                        '_type': recv[11],
                        'cmd': recv[12],
                    }
                    return r
                else:
                    print("校验和错误: 理论：%s 实际:%s"%(s,_sum))
                    return False
            else:
                return False
        else:
            return False

class Node:
    name     = ''
    cid      = ''
    number   = ''
    version  = ''
    lora_addr= ''
    position = ''
    status   = ''
    alive    = ''

    heart_time = 0
    join_time = 0

class NodesManager:

    def __init__(self):
        #  self.nodes = []
        #  self.nodes = sql.get_nodes()
        #  print(self.nodes)
        sql = Sql()

        nodes = sql.get_nodes()
        self.nodes = {}
        for n in nodes:
            node = Node()
            node.name=n.name
            node.cid=n.cid
            node.number=n.number
            node.lora_addr=n.lora_addr
            node.position=n.position
            node.version=n.version
            node.status=n.status
            node.alive=n.alive
            node.heart_time=time.time()
            self.nodes[n.cid] = node

        print(self.nodes)
        #  self.nodes = []
        self.cid = None
        self.heart_check_thread = Thread(target=self.heartCheckThread)
        self.heart_check_thread.setDaemon(True)
        self.heart_check_thread.start()

    def modify_node_alive(self, cid='', alive=False):
        for n in self.nodes:
            if n['cid'].upper() == cid.upper():
                print('%s设备在线状态：%s'%(cid, alive))
                n['alive'] == alive
                break

    def online(self, cid=''):
        if cid:
            if self.nodes.get(cid, None):
                self.nodes[cid].join_time = time.time()
                self.nodes[cid].heart_time = time.time()
                self.nodes[cid].alive = True
                print("设备>>>[%s]接入终端："%cid)
            else:
                print("设备[%s]未注册!!!"%cid)

    def heart_update(self, cid=''):
        if cid in self.nodes.keys():
            print("更新心跳时间为:",time.time())
            self.nodes[cid].heart_time = time.time()
            self.nodes[cid].alive = True
        else:
            print("设备未注册，请注册节点到终端中")

    def heartCheckThread(self):
        while True:
            time.sleep(3)
            for k in self.nodes.keys():
                if time.time() - self.nodes[k].heart_time >= 9:
                    print('设备>>>[%s]不在线,心跳超时!!!'%k)
                    self.nodes[k].alive = False
                    #  del self.nodes[k]

nodes = NodesManager()


class Ser:
    def __init__(self):
        self.configfile = os.getcwd()+'/conf/serial.conf'
        self.ser_init()
        self.find()
        # self.find_open()
        # mqtt
        self.mqtt = None
        self.nodes = None
        # 命令处理
        self.cmd = None

        self.nodes_heart = {}
        self.heart_check_alive = True
        self.thread_heart_check = Thread(target=self.heartCheckThread)
        self.thread_heart_check.setDaemon(True)
        self.thread_heart_check.start()

    def ser_init(self):
        '''初始化串口'''
        self.serial = serial.Serial()
        self.serial.timeout = 0.5# make sure that the alive event can be checked from time to time
        self.serial_alive = False
        self.serial.port = None
        self.serial.baudrate = 115200
        self.serial.bytesize = 8
        self.serial.parity   = 'N'
        self.serial.stopbits = 1
        # self.serial.dtr      = True
        # self.serial.rts      = True
        self.recv = ''
        self.recv = []
        self.thread_recv = None
        # 串口查询线程
        self.thread_find_open = Thread(target=self.find_open)
        self.thread_find_open.setDaemon(True)
        self.thread_find_open.start()

    def find(self):
        """查询串口"""
        p = None
        port_list = serial.tools.list_ports.comports()
        port_list_info = []
        for p in port_list:
            print('*'*50)
            print(str(p.device))
            print(str(p.serial_number))
        print('*' * 50)

    def set_default_serial(self, device='', serial_number='', baudrate=''):
        """设置默认串口"""
        try:
            config = configparser.RawConfigParser()
            # 添加condition选项
            config.add_section('serial')
            config.set("serial", "device", device)
            config.set("serial", "serial_number", serial_number)
            config.set("serial", "baudrate", baudrate)
            # 写入并保存文件到当前项目文件夹下
            with open(self.configfile, 'w') as configfile:
                config.write(configfile)
        except Exception as e:
            print(e)

    def find_open(self):
        """打开串口
        采用configparser读取配置文件：/conf/serial.ini, 从中获取串口设备的硬件ID[serial_number]，
        再根据当前查询到的串口外设[port_list]进行比对，打开满足设定的串口外设
        """
        # 读取默认串口配置文件
        while not self.serial_alive :
            time.sleep(1)
            config = configparser.ConfigParser()
            config.read(self.configfile)
            port_list = serial.tools.list_ports.comports()
            port = ''
            baudrate = ''
            for p in port_list:
                if str(p.serial_number) == config['serial']['serial_number']:
                    port = p.device
                    baudrate = config['serial']['baudrate']
                    self.set_default_serial(p.device, p.serial_number, self.serial.baudrate)
                    break
            if port and baudrate:
                try:
                    self.serial.port = port
                    self.serial.baudrate = baudrate
                    self.serial.open()
                except serial.SerialException as e:
                    print("Open Seial Error ：%s" % e)
                    logging.debug("Open Seial Error ：%s"%e)
                else:
                    self.serial_alive = True
                    self.thread_recv = Thread(target=self.recv_hander)
                    self.thread_recv.setDaemon(True)
                    self.thread_recv.start()
                    # self.thread_write = Thread(target=self.write_hander)
                    # self.thread_write.setDaemon(True)
                    # self.thread_write.start()
                    print("打开串口: %s / %s"%(self.serial.port, self.serial.baudrate))
                    logging.debug("打开串口: %s / %s"%(self.serial.port, self.serial.baudrate))
            else:
                print("串口设备未发现")
                logging.debug("串口设备未发现")

    def recv_hander(self):
        """串口接收, 串口端口异常情况下，关闭串口，并重新开启串口查询功能"""
        while self.serial_alive:
            try:
                n = self.serial.inWaiting()
                if n:
                    b = self.serial.read(n)
                    # bytes转为16进制字符串
                    s = str(binascii.b2a_hex(b).decode('utf-8')).upper()
                    cmd_list = [c for c in b]
                    self.cmd_deal(s)
            except Exception as e:
                print(e)
                logging.warning(e)
                print("串口异常")
                # 关闭串口
                self.serial_alive = False
                self.serial.close()
                print("串口关闭")
                # 重新打开串口查询
                self.thread_find_open = Thread(target=self.find_open)
                self.thread_find_open.setDaemon(True)
                self.thread_find_open.start()
                print("重新查询串口")

    def cmd_deal(self, recv):
        '''串口处理命令，监测校验和是否正确'''
        if recv.startswith('AA'):
            data = recv[4:-2]
            theory_len =  int(recv[2:4], 16)
            real_len = int(len(data)/2)
            if theory_len == real_len:
                data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
                s = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
                _sum = recv[-2:]
                if s == _sum:
                    addr    = data[0:8]
                    _type   = data[8:10]
                    cmd     = data[10:12]
                    content = data[12:]
                    if addr == localhost_cid:
                        if cmd == "B3" and content:
                            print("获取节点连接请求")
                            self.cmd_send(
                                target=content,
                                _type='01',
                                cmd='B3',
                            )
                            # 节点添加设备
                            nodes.online(cid=content.lower())
                    if cmd == "B0":
                        print('接收到心跳包')
                        nodes.heart_update(addr.lower())
                        #  sql = Sql()
                        #  sql.modify_node_alive(cid=addr,alive=True)
        return False

    def cmd_send(self, lora_addr='', target='',local='',_type='',cmd='',content=''):
        header = 'AA'
        data = target+local+_type+cmd+content
        length = hex(int(len(data)/2)).replace('0x','').zfill(2)[-2:]
        if len(data)>0 and len(data) % 2 == 0:
            data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
            _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
            cmd = lora_addr+header+length+data+_sum
            print(cmd)
            try:
                self.write(cmd.upper(), 'hex')
            except Exception as e:
                print(e)

    def heartCheckThread(self):
        """心跳监测"""
        while self.heart_check_alive:
            time.sleep(3)
            for h in self.nodes_heart:
                print(h)
                print(time.time()- h['time'])
            #  self.nodes_heart

    def recv_deal(self):
        '''串口处理命令，监测校验和是否正确'''
        self.recv = self.recv
        start = self.recv.find("AA")
        startwith = self.recv.startswith('AA')
        def find_last(string, s):
            last_position = -1
            while True:
                position = string.find(s, last_position + 1)
                if position == -1:
                    return last_position
                last_position = position
        start_num = len(re.compile('.*?(AA).*?').findall(self.recv))
        if self.recv.startswith('AA'):
            for i in re.compile('.*?(AA).*?').findall(self.recv):
                if not len(self.recv)>4:
                    return
                else:
                    theory_len = int(self.recv[2:4], 16)
                    real_len = len(self.recv) / 2 - 3
                    if not theory_len <= real_len:
                        return
                    else:
                        end = (theory_len+3)*2
                        cmd = self.recv[start:end]
                        self.recv = self.recv[end:]
                        s = 0
                        cmd_sum = cmd[-2:]
                        cmd_data = cmd[4:-2]
                        for i in range(0, len(cmd_data)):
                            if i % 2 == 0:
                                s = s + int(cmd_data[i:i + 2], 16)
                        s = hex(s).upper()[-2:]
                        if s == cmd_sum :
                            print('校验和正确')
                            self.cmd_deal(cmd_data)
                        else:
                            self.recv = ''
                            print('校验错误！！！！！！！！')
                            print('正确：', s, '错误:', cmd_sum)
        else:
            self.recv = self.recv[self.recv.find('AA'):]

    def str_to_hex(self, s):
        """字符串转16进制"""
        return ''.join([hex(ord(c)).replace('0x', '').zfill(2) for c in s])

    def write(self, msg, base='str'):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                # 十六进制格式字符串转bytes
                # print(type(msg))
                if base == 'str':
                    self.serial.write(msg.encode('utf-8'))
                if base == 'hex':
                    self.serial.write(binascii.unhexlify(msg))
                print("串口发送命令：%s"%msg)
        else:
            print("serial is not open!!!")

    def writeBytes(self, msg):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                try:
                    self.serial.write(msg)
                    print("串口发送命令：%s"%msg)
                except Exception as e:
                    print(e)
        else:
            print("serial is not open!!!")

    def sendToUrl(self, msg):
        print("发送消息到网页")
        url = 'http://localhost:5000/serial_recv'
        body = {"msg": msg}
        headers = {'content-type': "application/json"}
        response = requests.post(url, data = json.dumps(body), headers = headers)


import paho.mqtt.client as mqtt
class Mqtt:
    def __init__(self):
        # mqtt配置
        self.configfile = os.getcwd()+'/conf/mqtt.conf'
        config = configparser.ConfigParser()
        config.read(self.configfile)
        mqtt = config['mqtt']
        # 配置设置
        self.host = mqtt['host']
        self.port = int(mqtt['port'])
        self.admin = mqtt['admin']
        self.password = mqtt['password']
        self.client_id = mqtt['client_id']
        # 串口
        self.ser = None
        # 节点
        self.nodes = None
        # 命令处理
        self.cmd = None

    def client_loop(self):
        self.client = mqtt.Client(self.client_id)  # ClientId不能重复，所以使用当前时间
        self.client.username_pw_set(self.admin, self.password)  # 必须设置，否则会返回「Connected with result code 4」
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        try:
            self.client.connect(self.host, self.port, 60)
            self.client.loop_forever()
        except Exception as e:
            print(e)

    def on_connect(self, client, userdata, flags, rc):
        """客户端订阅消息
        多个主题订阅：
            [("topic1", qos), ("topic2", qos)]
        单个主题订阅: "topic1", qos
        """
        print("Connected with result code " + str(rc))
        logging.info("Connected with result code " + str(rc))
        # topic_list = self.sub_topic.split(';')
        # topic_list = [(topic_list[i], 0) for i in range(0, len(topic_list)) if topic_list[i]]
        # nodes = self.nodes.nodes
        # topic = ["topic1"]
        # if nodes:
            # for n in nodes:
                # topic.append((n['cid'],1))
        self.client.subscribe("17740171896",qos=0)
        print("Subscribe List：", topic)
        # self.on_publish_more("%s:online"%self.equ_uuid)

    def on_publish(self, topic, payload, qos=0):
        """客户端推送消息

        topic: 推送主题
        payload: 推送内容/负载
        qos: 服务质量等级，
            qos0:发送者只发送一次消息，不进行重试，Broker不会返回确认消息
            qos1:发送者最少发送一次消息，确保消息到达Broker，Broker需要返回确认消息PUBACK
            qos2(相比qos1开销很大):使用两阶段确认来保证消息的不丢失和不重复
        """
        print('MQTT Publish : [%s] %s' % (topic, payload))
        logging.info('MQTT Publish : [%s] %s' % (topic, payload))
        self.client.publish(topic, payload, qos)

    def on_message(self, client, userdata, msg):
        """客户端接收订阅的消息

        client: 客户端
        userdata: 用户数据
        msg: 接收数据
            msg.topic: 接收消息的主题名称
            msg.pyload: 接收到消息的内容/负载
        """
        topic = str(msg.topic)
        message = str(msg.payload.decode("utf-8"))
        pyload = msg.payload
        # print(msg.clientID)
        print('-'*20+"MQTT接收："+'-'*20)
        print("MQTT Recv:\r\n\ttopic:{topic}\r\n\tmsg:{msg}".format(topic=topic, msg=message))
        logging.info("MQTT Recv:\r\n\ttopic:{topic}\r\n\tmsg:{msg}".format(topic=topic, msg=message))
        localhost_cid = "53010000"
        cmd = message.split('/')
        node = sql.node_filter(cid=cmd[0])
        if node:
            if node.alive:
                print('设备离线')
                if cmd[1] == '01' and cmd[2] == 'B1':
                    print('打开阀门')
                if cmd[1] == '01' and cmd[2] == 'B2':
                    print('关闭阀门')
                self.ser_send(
                    lora_addr = node.lora_addr,
                    target = cmd[0],
                    _type = cmd[1],
                    cmd = cmd[2])
            else:
                print('设备离线、异常')
                self.on_publish(localhost_cid,'00/01',qos=0)
        else:
            print('设备不存在')
            self.on_publish(localhost_cid,'00/02',qos=0)

        # if node['alive']=="true":
            # print('匹配到设备:\r\n\t ', node)
            # addr = node['lora_addr']
            # if message == "MT+TAP=1": # 打开阀门
                # print("打开阀门")
                # res = self.cmd.get_cmd(_type=0x01, cmd=0xB1)
                # res.insert(0, int(addr[0:2], 16))
                # res.insert(1, int(addr[2:4], 16))
                # res.insert(2, int(addr[4:6], 16))
                # self.ser.writeBytes(bytearray(res))
                # # self.ser_send(lora_addr = node['lora_addr'], cmd='AA 05 FF 00 01 01 00 01')
            # if message == "MT+TAP=0": # 关闭阀门
                # print("关闭阀门")
                # res = self.cmd.get_cmd(_type=0x01, cmd=0xB2)
                # res.insert(0, int(addr[0:2], 16))
                # res.insert(1, int(addr[2:4], 16))
                # res.insert(2, int(addr[4:6], 16))
                # self.ser.writeBytes(bytearray(res))
        print('-' * 50,end='\r\n')

    def ser_send(self, lora_addr='', target='',local='',_type='',cmd='',content=''):
        header = 'AA'
        data = target+local+_type+cmd+content
        length = hex(int(len(data)/2)).replace('0x','').zfill(2)[-2:]
        if len(data)>0 and len(data) % 2 == 0:
            data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
            _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
            cmd = lora_addr+header+length+data+_sum
            print(cmd)
            try:
                self.ser.write(cmd.upper(), 'hex')
            except Exception as e:
                print(e)

def run():
    cmd = Cmd()
    ser = Ser()
    mqtt = Mqtt()
    ser.mqtt = mqtt
    ser.cmd = cmd
    mqtt.ser = ser
    mqtt.cmd = cmd
    mqtt.client_loop()

if __name__ == '__main__':
    # test()
    run()


